#include "Space.h"
#include "Drop.h"
#include "Dot.h"
#include "Wave.h"

#include <cstdlib>
#include <iostream>
#include <math.h>

RefractionWaves Drop::refraction(Wave &wave)
{
    // Находим точку входа в каплю
    // Координата x - решение уравнения (минимальное):
    // y[круга] +/- sqrt( -x[круга]^2 + 2*x[круга]*x + r^2 - x^2 ) = y[источника] - x[источника]*k + k * x
    // =>
    //  -x[круга]^2 + 2*x[круга]*x + r^2 - x^2 = (y[источника] - x[источника]*k + k * x - y[круга])^2
    // =>
    // b + c*x - x^2 = (k*x - a)^2
    // где a = y[круга] + x[источника] * k - y[источника]
    // b = r^2 - x[круга]^2
    // c = 2 * x[круга]
    double k = wave.getTangent(); 
    double a = k * wave.getSource().getX() - wave.getSource().getY() + position.getY();
    double b = (double)(radius * radius) - position.getX() * position.getX();
    double c = 2 * position.getX();
    
    int inputX = crossingEquation(k, a, b, c, false);
    
    if (inputX < 0) {
        throw RainbowException();
    }
    
    // Координата y - подставив найденный x в уравнение прямой:
    // y = y[источника] + k * (x - x[источника])
    int inputY = wave.getSource().getY() + wave.getTangent() * (inputX - wave.getSource().getX());
    
    wave.setEnding(Dot(inputX, inputY));
    
    // Пускаем волну2 с началом в inputX,inputY с коэф.:
    // k[волны2] = tg( arctg(k) - B * length )
    double inputTangent = calculateTangent(wave, true);
    Wave firstOutputWave(Dot(inputX, inputY), inputTangent, wave.getLength());
    
    // находим точку выхода новой волны по схеме, описанной выше (максимальное решение):
    k = inputTangent;
    a = k * inputX - inputY + position.getY();
    
    int outputX = crossingEquation(k, a, b, c, true);
    
    if (outputX < 0) {
        throw RainbowException();
    }
    
    int outputY = inputTangent * (outputX - inputX) + inputY;
    
    // Пускаем волну3
    // k[волны3] = tg( arctg(k[волны2]) - С * length )
    double innerTangent = calculateTangent(firstOutputWave, false);
    Wave innerWave(Dot(outputX, outputY), innerTangent, wave.getLength());

    
    // Находим точку входа2 (минимальное решение)
    k = innerTangent;
    a = k * outputX - outputY + position.getY();
    
    int secondInputX = crossingEquation(k, a, b, c, true);
    
    if (secondInputX < 0) {
        throw RainbowException();
    }
    
    int secondInputY = innerTangent * (secondInputX - outputX) + outputY;
    
    innerWave.setEnding(Dot(secondInputX, secondInputY));
    
    // пускаем волну4
    // k[волны4] = tg( arctg(k[волны3]) - B * length )
    double secondInputTangent = calculateTangent(innerWave, true);
    Wave secondOutputWave(Dot(secondInputX, secondInputY), secondInputTangent, wave.getLength());
          
    RefractionWaves waves(firstOutputWave, secondOutputWave, innerWave);
    return waves;
}

/**
 * Решает уравнение вида: (k*x - a)^2 = b + c*x - x^2
 * 
 * @see http://www.wolframalpha.com/input/?i=%28a+%2B+k*x%29%5E2+%3D+b+%2B+c*x+-+x%5E2
 * 
 * @param k
 * @param a
 * @param b
 * @param c
 * @param return_max если true, то будет возвращено максимальное решение уравнения, иначе - минимальное
 * @return положительное решение уравнения, или значение <0, если решения нет
 */
int Drop::crossingEquation(double k, double a, double b, double c, bool return_max)
{
    double descr = -4*a*a + 4*a*c*k + 4*b*k*k + 4*b + c*c;
    
    if (descr < 0) {
        return -1;
    }
    
    double first_x = (-sqrt(descr) + 2*a*k + c) / (2 * (k*k+1));
    double second_x = (sqrt(descr) + 2*a*k + c) / (2 * (k*k+1));
    
    if ( return_max && first_x > second_x || !return_max && first_x < second_x ) {
        return first_x;
    }
    else {
        return second_x;
    }
}

/**
 * Вычисляет тангенс волны после преломления: tangent = tg( degrees - B * length )
 * 
 * @param wave исходная волна
 * @param is_input есдт true, то происходит преломление при входе в каплю, иначе - при выходе из неё
 * @return тангенс угла наклона волны после преломления
 */
double Drop::calculateTangent(Wave wave, bool is_input)
{
    double tangent = wave.getTangent() * (1600.0 / (double)wave.getLength()) * (is_input ? 1 : -1);
    //std::cout << "Tangent: " << tangent << std::endl;
    return tangent;
    //double factor = is_input ? DROP_IMPUT_FACTOR : DROP_OUTPUT_FACTOR;
    //return tan( atan(wave.getTangent()) - factor * wave.getLength() );
}

/**
 * Пересекается ли волна с данной каплей
 * 
 * @param wave
 * @return 
 */
bool Drop::hasCrossing(Wave wave)
{
    double k = wave.getTangent(); 
    double a = k * wave.getSource().getX() - wave.getSource().getY() + position.getY();
    double b = (double)(radius * radius) - position.getX() * position.getX();
    double c = 2 * position.getX();
    
    int inputX = crossingEquation(k, a, b, c, false);
    
    return inputX > 0;
}

void Drop::selftest(void)
{
    std::cout << "DROP SELF TEST" << std::endl << std::endl;
    
    std::cout << "crossingEquation() ? " << std::endl;
    
    std::cout << "k=10; a=-73; b=19; c=34  =>  ";
    int result = crossingEquation(10, -73, 19, 34, true);
    int expect = -1;
    std::cout << "Result: " << result << "; Expect: " << expect << (result == expect ? " OK" : " ERROR") << std::endl;
    
    std::cout << "k=1; a=10; b=-96; c=20  =>  ";
    result = crossingEquation(1, 10, -96, 20, true);
    expect = 11;
    std::cout << "Result: " << result << "; Expect: " << expect << (result == expect ? " OK" : " ERROR") << std::endl;
    
    std::cout << "k=2; a=3; b=13; c=-7  =>  ";
    result = crossingEquation(2, 3, 13, -7, true);
    expect = 1;
    std::cout << "Result: " << result << "; Expect: " << expect << (result == expect ? " OK" : " ERROR") << std::endl;
    
    std::cout << "k=0.5; a=11; b=-391; c=40  =>  ";
    result = crossingEquation(0.5, 11, -391, 40, false);
    expect = 17;
    std::cout << "Result: " << result << "; Expect: " << expect << (result == expect ? " OK" : " ERROR") << std::endl;
    
    
    std::cout << std::endl << "hasCrossing() ?" << std::endl;
    
    std::cout << "Center: 10,10; Radius: 2; Tangent: 1  => ";
    Wave first(Dot(0,0), 1, 100);
    radius = 2;
    position = Dot(10, 10);
    bool crossingResult = hasCrossing(first);
    bool crossingExpect = true;
    std::cout << "Result: " << crossingResult << "; Expect: " << crossingExpect << (crossingResult == crossingExpect ? " OK" : " ERROR") << std::endl;
    
    
    std::cout << "Center: 30,13; Radius: 4; Tangent: 1/2  => ";
    Wave second(Dot(0,0), 0.5, 100);
    radius = 4;
    position = Dot(30, 13);
    crossingResult = hasCrossing(second);
    crossingExpect = true;
    std::cout << "Result: " << crossingResult << "; Expect: " << crossingExpect << (crossingResult == crossingExpect ? " OK" : " ERROR") << std::endl;
    
    
    std::cout << "Center: 30,30; Radius: 5; Tangent: 1/2  => ";
    Wave third(Dot(0,0), 0.5, 100);
    radius = 5;
    position = Dot(30, 30);
    crossingResult = hasCrossing(third);
    crossingExpect = false;
    std::cout << "Result: " << crossingResult << "; Expect: " << crossingExpect << (crossingResult == crossingExpect ? " OK" : " ERROR") << std::endl;
}
